(ns packet.utils
  (:require [clojure.string :as str]))

;;;以下所称字节均指无符号字节，即介于0到255的数

;;多字节数据字节序标志，默认高字节在前，这是网络传输的字节序
(def ^:dynamic *big-endian* true)

;;------------------------------------------------------------
;;辅助函数

(defn line-error
  "在宏中应用，抛出带行号的错误。解析器和构建器不会抛出错误，只会返回错误"
  [line msg & more]
  (let [info (cond-> {:line line} (not-empty more)
                     (assoc :info more))]
    (throw (ex-info msg info))))

(defn digit?
  "判断字符是否是数字"
  [c]
  (<= 0x30 (int c) 0x39))

(defn getf
  "得到序列中指定元素的下一个元素"
  [coll e]
  (second (drop-while #(not= % e) coll)))

(defn removef
  "移除序列中指定元素及其下一个元素"
  [coll e]
  (let [[s1 s2] (split-with #(not= % e) coll)]
    (concat s1 (nnext s2))))

(defn find-first
  "在集合coll中寻找第一个满足pred条件的元素"
  [pred? coll]
  (some #(when (pred? %) %) coll))

(defn find-last
  "在集合coll中寻找第一个满足pred条件的元素"
  [pred? coll]
  (find-first pred? (reverse coll)))

(defn remove-first
  [pred? coll]
  (let [[c1 c2] (split-with (complement pred?) coll)]
    (concat c1 (rest c2))))

(defn remove-last
  [pred? coll]
  (reverse (remove-first pred? (reverse coll))))

(defn reverse-split-with
  [pred? coll]
  (let [[xs1 xs2] (split-with pred? (reverse coll))]
    [(reverse xs2) (reverse xs1)]))

(defn keyword-map
  "将一个映射所有层次的键都关键字化"
  [m]
  (cond->> m (map? m)
           (reduce-kv (fn [m k v]
                        (let [k (cond-> k (or (symbol? k) (string? k))
                                        keyword)]
                          (assoc m k (keyword-map v))))
                      nil)))

(defn in?
  "判断某个值是否在序列中"
  [x & xs]
  (some #(= x %) xs))

(defn contains-in?
  "映射中是否存在指定的键序列，如
  (contains-in? {:a {:b nil}} [:a :b])返回true
  (contains-in? {:a :any} [:a :b])返回false"
  [m ks]
  (cond (not (map? m)) false
        (empty? ks) true
        :else (let [[k & ks] ks]
                (and (contains? m k)
                     (or (empty? ks)
                         (contains-in? (get m k) ks))))))

(defn positions
  "xs中满足条件pred的元素的索引序列"
  [pred xs]
  (keep-indexed #(when (pred %2) %1) xs))

(defn index-of
  "xs中第一个满足pred条件的元素的索引"
  [pred xs]
  (first (positions pred xs)))

(defn last-index-of
  "xs中最后一个满足pred条件的元素的索引"
  [pred xs]
  (index-of pred (reverse xs)))

;;-----------------------------------------------------
(defn valid-bcd?
  "判断一字节压缩bcd码的有效性"
  [byte]
  (and (<= 0 byte 0x99) (<= (mod byte 16) 9)))

(defn bcd->byte
  "一字节压缩bcd码转换为整数"
  [b]
  {:pre [(valid-bcd? b)]
   :post [(<= 0 % 99)]}
  (let [h (quot b 16) l (rem b 16)]
    (-> h (* 10) (+ l) unchecked-byte)))

(defn byte->bcd
  "一字节整数转换为压缩bcd码"
  [i]
  {:pre [(<= 0 i 99)]
   :post [(valid-bcd? %)]}
  (let [h (quot i 10) l (rem i 10)]
    (-> h (* 16) (+ l))))

(defn byte->hex
  "一字节整数转换为用两个16进制字符表示的字符串"
  [b]
  {:pre [<= 0 b 255]}
  (->> [(quot b 16) (rem b 16)]
       (map #(char (+ % (if (> % 9) 0x57 0x30))))
       (apply str)))

(defn hex->byte
  "16进制字符串转换为1字节整数"
  [s]
  {:pre [(<= (count s) 2)]
   :post [(<= 0 % 255)]}
  (reduce (fn [n c]
            (let [i (int c)
                  i (cond (<= 97 i 102) (- i 87)
                          (<= 65 i 70) (- i 55)
                          (<= 48 i 57) (- i 48)
                          :else (throw (ex-info "无效字符" {:cause c})))]
              (-> n (* 16) (+ i))))
          0
          s))
;;----------------------------------------------------------------
(defn hex-string->bytes
  "空格分隔的16进制字符串转换为字节序列"
  [s]
  (when-not (empty? s)
    (->> (str/split (str/trim s) #"\s+")
         (mapcat #(let [s (cond->> % (odd? (count %)) (cons \0))]
                    (partition 2 s)))
         (map hex->byte))))

(defn bytes->hex-string
  "字节序列转换为16进制字符串"
  [bs]
  (->> (map byte->hex bs)
       (interpose \space)
       (apply str)))

(defn bytes->uint
  "字节序列转换为无符号整数"
  [bs]
  (reduce #(-> %1 (* 256) (+ %2))
          0
          (cond-> bs (not *big-endian*) reverse)))

(defn uint->bytes
  "无符号整数转换为字节序列"
  [v len]
  (when (pos? len)
    (if (int? v)
      (cond-> (->> v
                   (iterate #(unsigned-bit-shift-right % 8))
                   (map #(bit-and % 0xff))
                   (take len))
        *big-endian* reverse)
      (throw (ex-info "参数不是非负整数" {:arg v})))))

(defn bytes->int
  "字节序列转换为有符号整数"
  [bs]
  (if (empty? bs)
    0
    (let [bs (cond-> bs (not *big-endian*) reverse)]
      (reduce #(-> %1 (bit-shift-left 8) (bit-or %2))
              (long (unchecked-byte (first bs)))
              (rest bs)))))

(defn int->bytes
  "有符号整数转换为字节序列"
  [v len]
  (when (pos? len)
    (if (int? v)
      (cond-> (->> v
                   (iterate #(bit-shift-right % 8))
                   (map #(bit-and % 0xff))
                   (take len))
        *big-endian* reverse)
      (throw (ex-info "参数不是整数" {:arg v})))))

(defn bcd+->uint
  "bcd序列转换为无符号整数"
  [bs]
  (reduce #(if (valid-bcd? %2)
             (-> %1 (* 100) (+ (bcd->byte %2)))
             (reduced nil))
          0
          (cond-> bs (not *big-endian*) reverse)))

(defn uint->bcd+
  "无符号整数转换为bcd序列"
  [v len]
  (when (pos? len)
    (if (int? v)
      (cond-> (->> v
                   (iterate #(quot % 100))
                   (map #(byte->bcd (rem % 100)))
                   (take len))
        *big-endian* reverse)
      (repeat len 0xff))))

(defn bcd+->int
  "bcd序列转换为有符号整数"
  [bs]
  (if (empty? bs)
    0
    (let [bs (cond-> bs (not *big-endian*) reverse)
          fb (first bs)
          n? (bit-test fb 7)
          bs (cons (bit-clear fb 7) (rest bs))
          v (reduce #(if (valid-bcd? %2)
                       (-> %1 (* 100) (+ (bcd->byte %2)))
                       (reduced nil))
                    0
                    bs)]
      (cond-> v n? -))))

(defn int->bcd+
  "有符号整数转换为bcd序列"
  [v len]
  (when (pos? len)
    (if (int? v)
      (let [n? (neg? v)
            value (cond-> v n? -)
            bs (->> (iterate #(quot % 100) value)
                    (map #(byte->bcd (rem % 100)))
                    (take len))
            bs (cond->> bs n?
                        (map-indexed (fn [i b]
                                       (cond-> b (= i (dec len))
                                               (bit-set 7)))))]
        (cond-> bs *big-endian* reverse))
      (repeat len 0xff))))

(defn leader-partition
  "将满足条件的元素及其后面的元素分在同一组，直到遇到另一个满足条件的元素为止"
  [pred coll]
  (when-not (empty? coll)
    (lazy-seq
     (let [[x & xs] coll
           ss (if (pred x)
                (cons x (take-while #(not (pred %)) xs))
                (take-while #(not (pred %)) coll))
           n (count ss)]
       (cons ss (leader-partition pred (drop n coll)))))))

(defn flat-all
  "深度提取满足条件的元素，包括coll自己"
  [pred coll]
  (cond->> (when (coll? coll) (mapcat #(flat-all pred %) coll)) 
    (pred coll) (cons coll)))


